#include "liobase.h"

#pragma comment( lib, "opengl32.lib" )				// Search For OpenGL32.lib While Linking
#pragma comment( lib, "glu32.lib" )					// Search For GLu32.lib While Linking

namespace Lio {

WORD Caller::CALLID=0;

void beep()
{
	MessageBeep(0);	
}
void message(CSTR fmt, ...)
{
	if (fmt == NULL)
		return;
	_TCHAR text[1024];
	va_list ap;
	va_start(ap, fmt);
		_vsntprintf(text, 1024, fmt, ap);
	va_end(ap);
	MessageBox (NULL, text, _T("Message"), MB_OK | MB_ICONEXCLAMATION);
}
BOOL question(CSTR fmt, ...)
{
	if (fmt == NULL)
		return FALSE;
	_TCHAR text[1024];
	va_list ap;
	va_start(ap, fmt);
		_vsntprintf(text, 1024, fmt, ap);
	va_end(ap);
	return MessageBox(NULL,text, _T("Question"),MB_YESNO|MB_ICONQUESTION)!=IDNO;
}

HICON setCursor(CSTR name)
{
	HCURSOR old=GetCursor();
	SetCursor(LoadCursor(NULL,name));
	return old;
}
static _TCHAR errstr[MAX_PATH];
CSTR getLastErrorStr(DWORD error)
{
	LPVOID lpMsgBuf=NULL;
	FormatMessage( 
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
		NULL,
		error,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
		(STR) &lpMsgBuf,
		0,
		NULL
	);
	ClearMemory(errstr,sizeof(errstr));
	if(lpMsgBuf)
	{
		_tcsncpy(errstr,(CSTR)lpMsgBuf,MAX_PATH);
		LocalFree(lpMsgBuf);
	}
	return errstr;
}
BOOL Console::open(CSTR title, WORD rows, WORD cols)
{
	DWORD error=0;
	if(console)
		return TRUE;
	if(AllocConsole())
	{
		hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
		hInput=GetStdHandle(STD_INPUT_HANDLE);
		hError=GetStdHandle(STD_ERROR_HANDLE);
		if(	hOutput!=INVALID_HANDLE_VALUE&&
			hInput!=INVALID_HANDLE_VALUE&&
			hError!=INVALID_HANDLE_VALUE)
		{
			console=TRUE;
			dOutput=_open_osfhandle((intptr_t)hOutput,_O_TEXT);
			fOutput=_fdopen(dOutput,"w");
			setvbuf(fOutput,NULL,_IONBF,0);
			dInput=_open_osfhandle((intptr_t)hInput,_O_TEXT);
			fInput=_fdopen(dInput,"r");
			setvbuf(fInput,NULL,_IONBF,0);
			dError=_open_osfhandle((intptr_t)hError,_O_TEXT);
			fError=_fdopen(dError,"w");
			setvbuf(fError,NULL,_IONBF,0);
			if(title)
				SetConsoleTitle(title);
			if(GetConsoleScreenBufferInfo(hOutput,&coninfo))
			{
				if(rows)
					coninfo.dwSize.Y = rows;
				if(cols)
					coninfo.dwSize.X = cols;
				if(!SetConsoleScreenBufferSize(hOutput,coninfo.dwSize))
				{
					error=GetLastError();
					_ftprintf(fError,_T("CONSOLE BUFFER ERROR %d: %s\n"),error,getLastErrorStr(error));
				}
			}
			SET_DEBUG_OUT(fOutput);
			return TRUE;
		}
		CloseHandle(hOutput);
		CloseHandle(hInput);
		CloseHandle(hError);
		hOutput=NULL;
		hInput=NULL;
		hError=NULL;
		FreeConsole();
	}
	return FALSE;
}
void Console::close()
{
	if(console)
	{
		REM_DEBUG_OUT(fOutput);
		FreeConsole();
		console=FALSE;
		_close(dOutput);
		_close(dInput);
		_close(dError);
	}
	dOutput=-1;
	dInput=-1;
	dError=-1;
	hOutput=NULL;
	hInput=NULL;
	hError=NULL;
	fOutput=NULL;
	fInput=NULL;
	fError=NULL;
	ClearStruct(coninfo);
}
Application * Application::current=NULL;
Application::Application():
	allowSleep(TRUE),allowPower(TRUE),allowYield(TRUE),
	createFullScreen(FALSE),
	hInst(NULL),hPrevInst(NULL),
	cmdLine(NULL),cmdShow(NULL),
	cursor(NULL),created(FALSE),
	isUpdated(FALSE)
{
	INITRAND;
	assert(!current);
	current=this;
	process=GetCurrentProcess();
	ClearStruct(window);
	ClearStruct(display);
	ClearStruct(mouse);
	ClearStruct(keyboard);
}
Application::~Application()
{
	current=NULL;
	if(created) // if the application is terminated after an exception it wouldn't be destroyed
		destroy();
}
BOOL changeResolution(WORD width, WORD height, BYTE bpp)
{
	DEVMODE dmScreenSettings;								// Device Mode
	ClearStruct(dmScreenSettings);							// Make Sure Memory Is Cleared
	dmScreenSettings.dmSize				= sizeof (DEVMODE);	// Size Of The Devmode Structure
	dmScreenSettings.dmPelsWidth		= width;			// Select Screen Width
	dmScreenSettings.dmPelsHeight		= height;			// Select Screen Height
	dmScreenSettings.dmBitsPerPel		= bpp;				// Select Bits Per Pixel
	dmScreenSettings.dmFields			= DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
	return ChangeDisplaySettings (&dmScreenSettings, CDS_FULLSCREEN) == DISP_CHANGE_SUCCESSFUL;													// Display Change Was Successful, Return True
}
BOOL Application::resetWindow()
{
	if(!window.hWnd||window.fullscreen||createFullScreen)
		return FALSE;
	initWindow();
	return MoveWindow(
		window.hWnd,
		window.rect.left,
		window.rect.top,
		window.rect.right-window.rect.left,
		window.rect.bottom-window.rect.top,
		TRUE);
}
void Application::initWindow()
{
	DWORD winStyle = WS_OVERLAPPEDWINDOW;
	DWORD winExtStyle = WS_EX_APPWINDOW;
	if(createFullScreen)
	{
		if(changeResolution(
			display.width,
			display.height,
			display.bpp))
		{// Seting Top Window To Be Covering Everything Else
			winStyle = WS_POPUP;										
			winExtStyle |= WS_EX_TOPMOST;
			window.left=0;
			window.top=0;
			window.width=display.width;
			window.height=display.height;
		}
		else
		{
			message(_T("Mode Switch Failed!\nCannot Use That Resolution: %d:%d:%d\n"),
					display.width,display.height,display.bpp);
			createFullScreen = FALSE;
		}
	}
	RECT winRect = {
		window.left,
		window.top,
		window.left+window.width,
		window.top+window.height
	};
	if(!createFullScreen)// Adjust Window, Account For Window Borders
		AdjustWindowRectEx (&winRect, winStyle, 0, winExtStyle);
	window.style=winStyle;
	window.extStyle=winExtStyle;
	window.fullscreen=createFullScreen;
	window.rect=winRect;
}
BOOL Application::createWindow()
{
	initWindow();
	window.hWnd = CreateWindowEx ( window.extStyle,						// Extended Style
								   name,								// Class Name
								   window.title,						// Window Title
								   window.style,						// Window Style
								   window.rect.left<0?0:window.rect.left,// Window X Position
								   window.rect.top<0?0:window.rect.top,	// Window Y Position
								   window.rect.right-window.rect.left,	// Window Width
								   window.rect.bottom-window.rect.top,	// Window Height
								   HWND_DESKTOP,						
								   0,									// No Menu
								   hInst,								
								   this);
	if (window.hWnd)												
	{
		window.hDC = GetDC (window.hWnd);
		if (window.hDC)	
		{
			PIXELFORMATDESCRIPTOR pfd =	
			{
				sizeof (PIXELFORMATDESCRIPTOR),
				1,							// Version Number
				PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER,
				PFD_TYPE_RGBA,
				display.bpp,
				0, 0, 0, 0, 0, 0,			// Color Bits Ignored
				0,							// No Alpha Buffer
				0,							// Shift Bit Ignored
				0,							// No Accumulation Buffer
				0, 0, 0, 0,					// Accumulation Bits Ignored
				display.depth,				// Z-Buffer (Depth Buffer)
				display.stencil,			// Stencil Buffer
				0,							// No Auxiliary Buffer
				PFD_MAIN_PLANE,				// Main Drawing Layer
				0,							// Reserved
				0, 0, 0						// Layer Masks Ignored
			};
			int PixelFormat = ChoosePixelFormat (window.hDC, &pfd);
			if (PixelFormat&&SetPixelFormat(window.hDC,PixelFormat,&pfd))
			{
				window.hRC=wglCreateContext(window.hDC);
				if(window.hRC)
				{
					if (wglMakeCurrent(window.hDC, window.hRC))
					{
						ShowWindow (window.hWnd, cmdShow);
						reshape ();	
						return TRUE;
					}
					wglDeleteContext (window.hRC);
					window.hRC = 0;	
				}
			}
			ReleaseDC (window.hWnd, window.hDC);
			window.hDC = NULL;
		}
		DestroyWindow (window.hWnd);
		window.hWnd = NULL;
	}
	return FALSE;
}
BOOL Application::destroyWindow()
{
	if (window.hWnd)													// Does The Window Have A Handle?
	{	
		if (window.hDC)													// Does The Window Have A Device Context?
		{
			wglMakeCurrent (window.hDC, NULL);							// Set The Current Active Rendering Context To Zero
			if (window.hRC)												// Does The Window Have A Rendering Context?
			{
				wglDeleteContext (window.hRC);							// Release The Rendering Context
				window.hRC = NULL;										// Zero The Rendering Context
			}
			ReleaseDC (window.hWnd, window.hDC);						// Release The Device Context
			window.hDC = NULL;											// Zero The Device Context
		}
		DestroyWindow (window.hWnd);									// Destroy The Window
		window.hWnd = NULL;												// Zero The Window Handle
	}
	if (window.fullscreen)												// Is Window In Fullscreen Mode
		ChangeDisplaySettings (NULL,0);									// Switch Back To Desktop Resolution
	ShowCursor (TRUE);													// Show The Cursor
	return TRUE;														// Return True
}
// Global Message Counter
DWORD Application::msgCount=0;
// Process Window Message Callbacks
LRESULT CALLBACK Application::WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	msgCount++;
	Application *app = (Application *)GetWindowLongPtr(hWnd,GWL_USERDATA);
	switch (uMsg)
	{
		case WM_SYSCOMMAND:
		{
			switch (wParam)												// Check System Calls
			{
				case SC_SCREENSAVE:	
				case SC_MONITORPOWER:
					if(!app->allowPower)
						return 0;
			}
			break;
		}
		case WM_CREATE:													// Window Creation
		{
			CREATESTRUCT* creation = (CREATESTRUCT*)(lParam);// Store Application Pointer
			app = (Application *)(creation->lpCreateParams);
			SetWindowLongPtr (hWnd, GWL_USERDATA, (LONG)(LONG_PTR)app);
			return 0;
		}
		case WM_CLOSE:													// Closing The Window
		{
			app->terminate();
			return 0;
		}
		case WM_PAINT:
		{
			app->redraw();
			break;
		}
		case WM_MOVE:
		{
			app->window.left = LOWORD(lParam);   // horizontal position 
			app->window.top = HIWORD(lParam);   // vertical position
			return 0;
		}
		case WM_SIZE:
		{
			switch (wParam)
			{
				case SIZE_MINIMIZED:
				{
					app->window.visible = FALSE;
				}
				return 0;
				case SIZE_MAXIMIZED:
				case SIZE_RESTORED:
				{
					app->window.visible = TRUE;
					app->window.width=LOWORD (lParam);
					app->window.height=HIWORD (lParam);
					app->reshape ();
				}
				return 0;
			}
			break;
		}
		case WM_TOGGLEFULLSCREEN:
		{
			app->toggleFullscreen();
			break;
		}
		case WM_USER_WAKE:
		{
			app->setUpdated(TRUE);
			break;
		}
		case WM_USER_CALL: 
		{
			app->setUpdated(app->listen(LOWORD(wParam),HIWORD(wParam),(void *)lParam));
			return 0;
		}
		case WM_CHAR:
		case WM_DEADCHAR:
		case WM_SYSDEADCHAR:
		case WM_SYSCHAR:
		{
			app->keyboard.waiting=FALSE;
			app->keyboard.symbol=(_TCHAR)wParam;
			app->setUpdated(app->input(KEY_INPUT_MSG));
			return 0;
		}
		case WM_SYSKEYDOWN:
		case WM_SYSKEYUP:
		case WM_KEYDOWN:
		case WM_KEYUP:
		{
			app->keyboard.waiting=TRUE;
			app->keyboard.code=(BYTE)wParam;
			app->keyboard.repeatCount=(SHORT)GET_BITS((DWORD)lParam,0,15);
			app->keyboard.scanCode=(BYTE)GET_BITS((DWORD)lParam,16,23);
			app->keyboard.extendKey=GET_BIT((DWORD)lParam,24);
			app->keyboard.alt=GET_BIT((DWORD)lParam,29);
			app->keyboard.repeated=GET_BIT((DWORD)lParam,30);
			app->keyboard.pressed=!GET_BIT((DWORD)lParam,31);
			app->keyboard.keys[(BYTE)wParam]=app->keyboard.pressed;
			app->input(KEY_INPUT_MSG);
			app->setUpdated(TRUE);
			return 0;
		}
		case WM_MOUSEMOVE:
		case WM_RBUTTONDBLCLK:
		case WM_RBUTTONDOWN:
		case WM_RBUTTONUP:
		case WM_LBUTTONDBLCLK:
		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_MBUTTONDBLCLK:
		case WM_MBUTTONDOWN:
		case WM_MBUTTONUP:
		case WM_MOUSEACTIVATE:
		case WM_MOUSEWHEEL:
		{
			app->mouse.x=LOWORD(lParam);
			app->mouse.y=HIWORD(lParam);
			app->mouse.wheel = ((short)HIWORD(wParam))/WHEEL_DELTA;			// wheel rotation
			app->mouse.lbutton=(wParam&MK_LBUTTON)!=0;
			app->mouse.mbutton=(wParam&MK_MBUTTON)!=0;
			app->mouse.rbutton=(wParam&MK_RBUTTON)!=0;
			app->mouse.shift=(wParam&MK_SHIFT)!=0;
			app->mouse.ctrl=(wParam&MK_CONTROL)!=0;
			app->mouse.move=(uMsg==WM_MOUSEMOVE);
			app->input(MOUSE_INPUT_MSG);
			app->setUpdated(TRUE);
			return 0;
		}
		default:
			if(app&&app->callback())
				return 0;
	}
	return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
BOOL Application::registerClass()
{
	WNDCLASSEX windowClass;
	ZeroMemory (&windowClass, sizeof (WNDCLASSEX));
	windowClass.cbSize			= sizeof (WNDCLASSEX);
	windowClass.style			= CS_HREDRAW|CS_VREDRAW|CS_OWNDC|CS_DBLCLKS;
	windowClass.lpfnWndProc		= (WNDPROC) WinProc;
	windowClass.hInstance		= hInst;
	windowClass.hbrBackground	= (HBRUSH)(COLOR_APPWORKSPACE);
	windowClass.hCursor			= cursor;
	windowClass.lpszClassName	= name;
	return RegisterClassEx (&windowClass);
}

void invalidParameterProc(	const wchar_t * expr,
							const wchar_t * func, 
							const wchar_t * file, 
							unsigned int line,
							uintptr_t)
{
#ifdef _DEBUG
	if(!question(_T("Error detected in expr '%s', function '%s',\n\
					file '%s', line %d.\n\
					Continue the application?"),
					expr,func,file,line))
		exit(EXIT_FAILURE);
#else
	expr;func;file;line;
#endif
}
int Application::main(	HINSTANCE hInstance, HINSTANCE hPrevInstance,
						LPCTSTR lpCmdLine, int nCmdShow)
{
#if WINVER>=0x0501
	_set_invalid_parameter_handler(invalidParameterProc);
#endif
	hInst = hInstance;
	hPrevInst = hPrevInstance;
	cmdLine = lpCmdLine;
	cmdShow = nCmdShow;

	cmdToken.set(lpCmdLine, _T(" "));

	display.width=800;
	display.height=600;
	display.bpp=32;
	display.depth=32;
	display.stencil=8;
	name=_T("Application");
	cursor=LoadCursor(NULL,IDC_ARROW);
	if (!create())
	{
		message(_T("Initialization Error!"));
		return -1;
	}
	created=TRUE;
	if (!hPrevInst&&!registerClass ())
	{
		message(_T("Register Class '%s' Failed!"),(CSTR)name);
		return -1;
	}
	window.title=name;
	if(!window.width)
		window.width=display.width;
	if(!window.height)
		window.height=display.height;
	runTime=loopTime=0;
	isProgramLooping=TRUE;
	loopTimer.restart();

	__try
	{
		while (isProgramLooping)
		{
			if (createWindow())
			{
				if (!glCreate())
				{
					terminate();
				}
				else
				{
					isUpdated=TRUE;
					for(;;)
					{
						if (PeekMessage (&msg, window.hWnd, 0, 0, PM_REMOVE) != 0)
						{
							if (msg.message == WM_QUIT)
								break;
							TranslateMessage(&msg);
							DispatchMessage (&msg);
							continue;
						}
						runTime+=loopTime=loopTimer.restart();
						update();
						if(isUpdated&&window.visible)
						{
							draw();
							SwapBuffers(window.hDC);
							isUpdated=FALSE;
						}
						else if(allowSleep)
						{
							WaitMessage();
							continue;
						}
						if(allowYield)
						{
							Sleep(0);
						}
					}
				}	
				glDestroy ();
				destroyWindow();
			}
			else
			{
				message(_T("Error Creating Window!"));
				isProgramLooping = FALSE;
			}
		}

	}
	__except(EXCEPTION_HANDLER)
	{}

	destroy();
	created=FALSE;
	UnregisterClass (name, hInstance);

	return 0;
}
Application *Application::getCurrent()
{
	return current;
}
FileDialog::FileDialog():hWnd(NULL),drive(0),filterIndex(0),filterStr(MAX_PATH)
{
	path[0]=0;
}
FILE* FileDialog::openFile(CSTR mode)
{
	file.close();
	if(!path||!path[0]||!file.open(path,mode))
		return NULL;
	return file;
}
BOOL FileDialog::addFilter(CSTR ext, CSTR info)
{
	FilterObj obj=filters.insert();
	if(obj)
	{
		filter=&obj->value;
		filter->init(ext,info);
		return TRUE;
	}
	return FALSE;
}
CSTR FileDialog::getFilterStr()
{
	if(!filters.length)
		return NULL;
	FilterObj obj;
	filterStr.clear();
	for(obj=filters.first;obj;obj=obj->next)
	{
		filter=&obj->value;
		filterStr+=filter->info;
		filterStr+=_T(" (*.");
		filterStr+=filter->ext;
		filterStr+=_T(")");
		filterStr+='\0';
		filterStr+=_T("*.");
		filterStr+=filter->ext;
		filterStr+='\0';
	}
	/*
	filterStr+=_T("All files (*.*)");
	filterStr+='\0';
	filterStr+=_T("(*.*)");
	filterStr+='\0';
	*/
	filterStr+='\0';
	return filterStr;
}
void FileDialog::fill(OPENFILENAME *ofn, DWORD flags)
{
	ZeroMemory(ofn, sizeof(OPENFILENAME));
    ofn->lStructSize = sizeof (OPENFILENAME);
    ofn->hwndOwner = hWnd;
	ofn->lpstrFilter = getFilterStr();
	ofn->nFilterIndex = filterIndex;
    ofn->lpstrFile = path;
    ofn->nMaxFile = MAX_PATH;
    ofn->Flags = flags | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
	if(fileDir.length()>0)
		ofn->lpstrInitialDir=fileDir;
    ofn->lpstrDefExt = defExt;
}
void FileDialog::parse()
{
	filterIndex=ofn.nFilterIndex;
	GetShortPathName(path,shortPath,MAX_PATH);
	drive=path[0];
	fileExt=_T("");
	fileDir=_T("");
	fileName=path+ofn.nFileOffset;
	if(ofn.nFileExtension>0)
	{
		nameOnly.copy(path+ofn.nFileOffset,ofn.nFileExtension-ofn.nFileOffset-1);
		fileExt=path+ofn.nFileExtension;
	}
	else
	{
		nameOnly.copy(fileName);
	}
	fileDir.copy(path,ofn.nFileOffset);
}
CSTR FileDialog::open()
{
	fill(&ofn,OFN_FILEMUSTEXIST);
    if(!GetOpenFileName(&ofn))
	{
		//CSTR err=getLastErrorStr();
		return NULL;
	}
	parse();
	return path;
}
CSTR FileDialog::save(CSTR filePath)
{
	if(filePath)
		_tcsncpy(path,filePath,MAX_PATH);
	OPENFILENAME ofn;
	fill(&ofn);
    if(!GetSaveFileName(&ofn))
		return NULL;
	parse();
	return path;
}
void Directory::set(CSTR path)
{
	reset();
	int length=strLen(path);
	String str=path;
	if(length > 0 && path[length-1] != _T('\\'))
		str+=_T('\\');
	str+="*.*";
	name=str;
}
CSTR Directory::next()
{
	if(!h)
	{
		h = FindFirstFile(name,&data);
		if(h==INVALID_HANDLE_VALUE)
		{
			h=NULL;
			return FALSE;
		}
	}
	else if(!FindNextFile(h,&data))
	{
		reset();
		return FALSE;
	}
	return data.cFileName;
}
BOOL Directory::getCurrent(String &current)
{
	DWORD size=GetCurrentDirectory(0,NULL);
	if(!size)
		return FALSE;
	current.setSize(size-1);
	return !!GetCurrentDirectory(size,current);
}
BOOL Directory::setCurrent(CSTR current)
{
	return SetCurrentDirectory(current);
}

String::String(int length):size(0),page(0),shrink(FALSE)
{
	if(setSize(length))page=length;
}
String::String(const String &s):size(0),page(0),shrink(FALSE)
{
	int l=copy(s);
	if(l)page=l;
}
String::String(CSTR s):size(0),page(0),shrink(FALSE)
{
	int l=copy(s);
	if(l)page=l;
}
String::String(CSTR s1, const String& s2):size(0),page(0),shrink(FALSE)
{
	int l=strLen(s1);
	setSize(l+s2.size);
	add(s1,l);
	add(s2);
}
String::String(const String& s1, CSTR s2):size(0),page(0),shrink(FALSE)
{
	int l=strLen(s2);
	setSize(s1.size+l);
	add(s1);
	add(s2,l);
}
String::String(const String& s1,const String& s2):size(0),page(0),shrink(FALSE)
{
	setSize(s1.size+s2.size);
	add(s1);
	add(s2);
}
String::String(const String& s1, _TCHAR s2):size(0),page(0),shrink(FALSE)
{
	setSize(s1.size+1);
	add(s1);
	add(s2);
}
String::String(_TCHAR s1, const String& s2):size(0),page(0),shrink(FALSE)
{
	setSize(1+s2.size);
	add(s1);
	add(s2);
}
int String::copy(CSTR s, int l)
{
	if(!s)return 0;
	if(s!=chars.elems)
	{
		STR str=setSize(l);
		if(!str)return 0;
		memmove(str,s,l*sizeof(_TCHAR));
	}
	size=l;
	chars.elems[size]=0;
	return l;
}
BOOL String::equals(CSTR s, int l)const
{
	if(!s)return FALSE;
	if(size!=l)return FALSE;
	return chars.equals(s,l,FALSE);
}
int String::add(CSTR s, int l)
{
	if(!s)return 0;
	STR str=expand(l);
	if(!str)return 0;
	memmove(str+size,s,l*sizeof(_TCHAR));
	size+=l;
	str[size]=0;
	return l;
}
int String::insert(CSTR s, int i, int l)
{
	if(!s)return 0;
	if(i<0||i>=size)
		return add(s,l);
	STR str=expand(l);
	if(!str)return 0;
	STR str1=str+i;
	memmove(str1+l,str1,(size-i)*sizeof(_TCHAR));
	memmove(str1,s,l*sizeof(_TCHAR));
	size+=l;
	str[size]=0;
	return l;
}
int String::remove(int i, int l)
{
	if(i<0||i>=size)return 0;
	STR str=chars.elems+i;
	if(l<0||l>size-i)
	{
		size=i;
		*str=0;
	}
	else
	{
		size-=l;
		memmove(str,str+l,(size-i+1)*sizeof(_TCHAR));
	}
	return size;
}
int String::substr(STR str, int i, int l)
{
	if(i<0||i>=size)return 0;
	if(l<0||l>size-i)l=size-i;
	_tcsncpy(str,chars.elems+i,l);
	str[l]=0;
	return l;
}
int String::indexOf(CSTR s, int i, int l)
{
	if(s&&l>0&&i>=0)
	{
		if(l==1)
			return indexOf(*s,i);
		STR str=chars.elems;
		while(i<=size-l)
		{
			if(!memcmp(str+i,s,l))
				return i;
			i++;
		}
	}
	return -1;
}
int String::lastIndexOf(CSTR s, int l) // todo: test me
{
	if(s&&l>0)
	{
		if(l==1)
			return lastIndexOf(*s);
		int i=size-l;
		STR str=chars.elems;
		while(i>0)
		{
			if(!memcmp(str+i,s,l))
				return i;
			i--;
		}
	}
	return -1;
}
STR String::print(CSTR fmt, ...)
{
	if(!fmt)return "";
	va_list ap;
	va_start(ap, fmt);
		size=_vsntprintf(chars.elems, chars.size-1, fmt, ap);
	va_end(ap);
	if(size<0)
	{
		size=chars.size-1;
		chars.elems[size]=0;
	}
	return chars.elems;
}
size_t String::write(FILE *file)const
{
	return fwrite(chars.elems,sizeof(_TCHAR),size,file);
}
size_t String::read(FILE *file)
{
	fseek(file, 0L, SEEK_END);
	setSize(ftell(file));
	fseek(file, 0L, SEEK_SET);
	size_t n=fread(chars.elems,sizeof(_TCHAR),page,file);
	size=(int)n;
	chars.elems[n]=0;
	return n;
}
STR String::setSize(int l)
{
	if(l<0||(l+1>chars.size||shrink)&&!chars.setSize(l+1))
		return NULL;
	size=0;
	page=l;
	chars.elems[l]=0;
	return chars.elems;
}
void String::destroy()
{
	chars.destroy();
	size=0;
}
void String::clear()
{
	chars.clear();
	size=0;
}
STR String::expand(int l)
{
	if(l<=0)
		return NULL;
	int new_size=size+l;
	int dl=new_size-chars.size+1;
	if(dl>0&&!chars.setSize(chars.size+max(dl,page),MEM_OP_KEEP))
		return NULL;
	return chars.elems;
}

int InitInfo::indexOf(CSTR name)
{
	int i;
	ListObject<Info> *info;
	if(name)
		for(i=0,info=infos.first;info;i++,info=info->next)
			if(info->value.name==name)
				return i;
	return -1;
}
Info* InitInfo::findInfo(CSTR name)
{
	ListObject<Info> *info;
	if(name)
		for(info=infos.first;info;info=info->next)
			if(info->value.name==name)
				return &info->value;
	return NULL;
}
Info* InitInfo::add(CSTR name, CSTR value)
{
	if(!name||indexOf(name)!=-1)
        return FALSE;
	Info *info=infos.add();
	if(info)
	{
		info->name=name;
		info->value=value;
	}
	return info;
}
Info* InitInfo::set(CSTR name, CSTR value)
{
	Info *info=findInfo(name);
	if(info)
		info->value=value;
	return info;
}
CSTR InitInfo::get(CSTR name, CSTR defValue)
{
	Info *info=findInfo(name);
	getError=!info;
	return getError?defValue:info->value;
}
void InitInfo::destroy()
{
	infos.destroy();
}
BOOL InitInfo::read(FILE *file, CSTR seps, CSTR lines, CSTR blanks)
{
	if(!file)
		return FALSE;

	long tmp, size;
	tmp = ftell(file);
	fseek(file, 0L, SEEK_END);
	size = ftell(file);
	fseek(file, tmp, SEEK_SET);

	if(!buff.setSize((int)(size-1)))
		return FALSE;
	if(!buff.read(file))
		return FALSE;
	return readStr(buff,seps,lines,blanks);
}
BOOL InitInfo::readStr(CSTR str, CSTR seps, CSTR lines, CSTR blanks)
{
	String nbuff(256),vbuff(256);
	_TCHAR symbol;
	BOOL result=TRUE;
	BOOL readvalue=FALSE;
	destroy();
	int i=0;
	while(symbol=str[i++])
	{
		if(strIndexOf(blanks,symbol)!=-1)
			continue;
		if(strIndexOf(seps,symbol)!=-1)
		{
			readvalue=TRUE;
		}
		else if(strIndexOf(lines,symbol)!=-1)
		{
			if(readvalue)
			{
				readvalue=FALSE;
				if(!add(nbuff,vbuff))
				{
					result=FALSE;
					break;
				}
				vbuff.clear();
				nbuff.clear();
			}
		}
		else
		{
			if(readvalue)
				vbuff+=symbol;
			else
				nbuff+=symbol;
		}
	}
	if(readvalue)
	{
		if(!add(nbuff,vbuff))
			result=FALSE;
	}
	return result;
}
BOOL InitInfo::write(FILE *file, CSTR sep, CSTR line)
{
	if(!file)
		return FALSE;
	writeStr(sep,line);
	return !!buff.write(file);
}
CSTR InitInfo::writeStr(CSTR sep, CSTR line)
{
	ListObject<Info> *obj=infos.first;
	int seplen=strLen(sep),
		linelen=strLen(line),
		len;
	buff.clear();
	while(obj)
	{
		len=obj->value.name.length();
		buff.add(obj->value.name);
		if(seplen)
			buff.add(sep,seplen);
		buff.add(obj->value.value);
		if(linelen)
			buff.add(line,linelen);
		obj=obj->next;
	}
	return buff.chars.elems;
}
BOOL Tokenizer::set(CSTR str, CSTR separator, CSTR blanks, BOOL clear)
{
	String buffer,*token;
	_TCHAR c;
	int sepchar=0,sepstart=0,tokchar=0;
	int sepLength=strLen(separator);
	int strLength=strLen(str);
	if(!sepLength||!strLength)
		return FALSE;
	if(clear)
		destroy();
	if(!buffer.setSize(strLength))
		return FALSE;
	for(int i=0;i<strLength;i++)
	{
		c=str[i];
		if(strIndexOf(blanks,c)!=-1)
			continue;
		if(c==separator[sepchar])
		{
			if(!sepchar)
				sepstart=tokchar;
			sepchar++;
			if(sepchar==sepLength)
			{
				if(sepstart)
				{
					if(!(token=add()))return FALSE;
					token->copy(buffer,sepstart);
				}
				sepchar=0;
				tokchar=0;
				continue;
			}
		}
		else
		{
			sepchar=0;
		}
		buffer[tokchar++]=c;
	}
	if(tokchar)
	{
		if(!(token=add()))return FALSE;
		token->copy(buffer,tokchar);
	}
	return TRUE;
}

static DWORD WINAPI __thread_proc(void * param)
{
	ThreadInfo *info=(ThreadInfo*)param;
	if(!info||!info->caller)
		return 1;
	HANDLE handle=GetCurrentThread();
	SetThreadPriority(handle,info->priority);
	SetThreadName(info->name);
	Runnable *caller=info->caller;
	DWORD result=caller->run(info);
	CloseHandle(handle);
	return result;
}
BOOL Runnable::start(const ThreadInfo *info)
{
	if( !info || !info->caller || !CreateThread(NULL,0,__thread_proc,(void *)info,0,NULL) )
		return FALSE;
	return TRUE;
}

BOOL Compressor::compress(const BYTE *vector, int length)
{
	if(!length||length<0)return FALSE;
	DWORD newLength=compressBound(length)+ZIP_HEADER_SIZE;
	if(!setSize(newLength))return FALSE;
	BYTE *data=elems+ZIP_HEADER_SIZE;
	int zError=::compress(data,&newLength,vector,length);
	if(zError!=Z_OK)return FALSE;
	size=newLength+ZIP_HEADER_SIZE;
	int *header = (int *)elems;
	header[0]=ZIP_HEADER_TAG;
	header[1]=length;
	return TRUE;
}
BOOL Compressor::decompress(const BYTE *vector, int length)
{
	length-=ZIP_HEADER_SIZE;
	if(!length||length<0)return FALSE;
	const int *header = (const int *)vector;
	if(header[0]!=ZIP_HEADER_TAG)return FALSE;
	int origLength=header[1];
	if(!setSize(origLength))
		return FALSE;
	const BYTE *data = vector+ZIP_HEADER_SIZE;
	DWORD newLength=origLength;
	int zError=uncompress(elems,&newLength,data,length);
	if(zError!=Z_OK)
		return FALSE;
	if((DWORD)origLength!=newLength)
	{
		assert(FALSE);
		size=newLength;
	}
	return TRUE;
}

}; // namespace Lio